using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using Barrage.Global;
using SlimDX;

namespace Barrage.GUI.Elements
{
    /// <summary>
    /// Event that fires off when a GUIColorPicker's value changes
    /// </summary>
    /// <param name="buttonId">GUIElement id</param>
    /// <param name="value">new value for the GUIColorPicker</param>
    public delegate void GUIColorPickerHandler(string buttonId, Color3 value);
    /// <summary>
    /// GUIColorPicker allows the user to select a color by using three color component sliders
    /// </summary>
    public class GUIColorPicker : GUISelectableElement
    {
        /// <summary>
        /// Enumeration for color components in the slider
        /// </summary>
        protected enum ColorComponent{
            /// <summary>
            /// Red component
            /// </summary>
            Red = 0,
            /// <summary>
            /// Green component
            /// </summary>
            Green = 1,
            /// <summary>
            /// Blue component
            /// </summary>
            Blue = 2
        };

        /// <summary>
        /// \internal The ColorPicker has 3 sub-elements, one for each color component
        /// </summary>
        protected override int SubElementsCount
        {
            get
            {
                return 3;
            }
        }

        /// <summary>
        /// The color component currently being selected for modification
        /// </summary>
        protected ColorComponent currentComponent;
        
        const string XML_WIDTH_ATTRIBUTE = "width";
        const string XML_HEIGHT_ATTRIBUTE = "height";

        const string XML_SLIDER_OFFSET_X_ATTRIBUTE = "sliderOffsetX";
        const string XML_SLIDER_OFFSET_Y_ATTRIBUTE = "sliderOffsetY";
        const string XML_SLIDER_SPACING_ATTRIBUTE = "sliderSpacing";

        const string XML_SLIDER_WIDTH_ATTRIBUTE = "sliderWidth";
        const string XML_SLIDER_HEIGHT_ATTRIBUTE = "sliderHeight";

        const string XML_TEXT_ATTRIBUTE = "text";

        /// <summary>
        /// Event that fires when the color value of the item changes
        /// </summary>
        public event GUIColorPickerHandler OnInteraction;

        /// <summary>
        /// Item size for mouse cursor click detection
        /// </summary>
        protected Vector2 size;

        /// <summary>
        /// Item size for mouse cursor click detection
        /// </summary>
        public Vector2 Size { get { return size; } }

        /// <summary>
        /// Vertical spacing between component sliders
        /// </summary>
        protected float sliderSpacing;
        /// <summary>
        /// Vertical spacing between component sliders
        /// </summary>
        public float SliderSpacing { get { return sliderSpacing; } }
        
        /// <summary>
        /// Offset at which sliders are placed
        /// </summary>
        protected Vector2 sliderOffset;
        /// <summary>
        /// Offset at which sliders are placed
        /// </summary>
        public Vector2 SliderOffset { get { return sliderOffset; } }

        Color3 pickedColor = new Color3();
        /// <summary>
        /// Gets/Sets the color value of the field
        /// </summary>
        public virtual Color3 PickedColor { get { return pickedColor; } set { pickedColor = value; } }

        /// <summary>
        /// Size of each color component slider
        /// </summary>
        protected Vector2 sliderSize;
        /// <summary>
        /// Size of each color component slider
        /// </summary>
        public Vector2 SliderSize { get { return sliderSize; } }


        bool hovering = false;
        /// <summary>
        /// Returns true if the mouse is currently over this element
        /// </summary>
        public bool Hovering { get { return hovering; } }

        float sliderSpeed = 0.5f;
        /// <summary>
        /// Speed at which the slider moves when using the gamepad / keyboard to change its value
        /// </summary>
        public float SliderSpeed { get { return sliderSpeed; } set { sliderSpeed = value; } }

        string text;
        /// <summary>
        /// Gets / Sets text label of the element
        /// </summary>
        public virtual string Text { get { return text; } set { text = value; } }

        bool dragging = false;
        /// <summary>
        /// Returns true if the user is currently holding the mouse button down after clicking on the slider (dragging the slider)
        /// </summary>
        public bool Dragging { get { return dragging; } set { dragging = value; } }

        /// <summary>
        /// Default size of the item. Can be overrided
        /// </summary>
        protected virtual Vector2 DefaultSize { get { return new Vector2(100, 100); } }

        /// <summary>
        /// Default size of the color component sliders. Can be overrided
        /// </summary>
        protected virtual Vector2 DefaultSliderSize { get { return new Vector2(50, 50); } }

        /// <summary>
        /// Default offset of the color component sliders. Can be overrided
        /// </summary>
        protected virtual Vector2 DefaultSliderOffset { get { return new Vector2(0, 0); } }

        /// <summary>
        /// Default spacing between color component sliders
        /// </summary>
        protected virtual float DefaultSliderSpacing { get { return 0; } }

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="layout">Layout that will hold this item</param>
        /// <param name="template">Template that will be used to render this item</param>
        /// <param name="xn">Xml Node that holds attribute values of this item</param>
        public GUIColorPicker(GUILayout layout, string template, XmlNode xn)
            : base(layout, template, xn)
        {
            size = GameMath.ParseVector2(XmlHelper.GetXMLAttribute(xn, XML_WIDTH_ATTRIBUTE), XmlHelper.GetXMLAttribute(xn, XML_HEIGHT_ATTRIBUTE), DefaultSize);
            sliderSize = GameMath.ParseVector2(XmlHelper.GetXMLAttribute(xn, XML_SLIDER_WIDTH_ATTRIBUTE), XmlHelper.GetXMLAttribute(xn, XML_SLIDER_HEIGHT_ATTRIBUTE), DefaultSliderSize);
            sliderOffset = GameMath.ParseVector2(XmlHelper.GetXMLAttribute(xn, XML_SLIDER_OFFSET_X_ATTRIBUTE), XmlHelper.GetXMLAttribute(xn, XML_SLIDER_OFFSET_Y_ATTRIBUTE), DefaultSliderOffset);
            sliderSpacing = XmlHelper.ParseXMLFloatAttribute(xn, XML_SLIDER_SPACING_ATTRIBUTE, DefaultSliderSpacing);
            text = Language.LanguageManager.Texts[XmlHelper.GetXMLAttribute(xn, XML_TEXT_ATTRIBUTE)];
        }

        /// <summary>
        /// Returns the current value of the specified component
        /// </summary>
        /// <param name="component">color component to check</param>
        /// <returns>A value between 0 and 1 telling the component value</returns>
        protected float ComponentValue(ColorComponent component)
        {
            switch (component)
            {
                case ColorComponent.Red:
                    return pickedColor.Red;
                case ColorComponent.Green:
                    return pickedColor.Green;
                case ColorComponent.Blue:
                    return pickedColor.Blue;
            }
            return 0;
        }
        /// <summary>
        /// \internal Updates matrices and item logic
        /// </summary>
        protected override void DoUpdate()
        {
            base.DoUpdate();
            currentComponent = (ColorComponent)CurrentSubElement;
            hovering = CheckIntersection();
            if (hovering) Layout.SelectedItemIndex = Order;
            CheckSliderClick();
            if (Selected)
            {
                if (Keyboard.Instance.KeyState(SlimDX.DirectInput.Key.LeftArrow) || Gamepad.AnyButtonStatus(SlimDX.XInput.GamepadButtonFlags.DPadLeft))
                    changeValues(-SliderSpeed * Timer.LastIntervalSeconds,true);
                    
                if (Keyboard.Instance.KeyState(SlimDX.DirectInput.Key.RightArrow) || Gamepad.AnyButtonStatus(SlimDX.XInput.GamepadButtonFlags.DPadRight))
                    changeValues(SliderSpeed * Timer.LastIntervalSeconds,true);

                changeValues(Gamepad.SumStickPositions(Stick.LeftStick).X * Timer.LastIntervalSeconds,true);


            }
        }

        /// <summary>
        /// Changes the value of the currently active component to the specified value
        /// </summary>
        /// <param name="amount">value to set the component</param>
        protected void changeValues(float amount)
        {
            changeValues(amount, false);
        }

        /// <summary>
        /// Changes the value of the currently active component to the specified value
        /// </summary>
        /// <param name="amount">value to modify the component</param>
        /// <param name="relative">tells if the specified value is absolute (direct assignment) or relative (added to current value)</param>
        protected void changeValues(float amount, bool relative)
        {
            switch (currentComponent)
            {
                case ColorComponent.Red:
                    if (relative) this.pickedColor.Red += amount;
                    else this.pickedColor.Red = amount;
                    break;
                case ColorComponent.Green:
                    if (relative) this.pickedColor.Green += amount;
                    else this.pickedColor.Green = amount;
                    break;
                case ColorComponent.Blue:
                    if (relative) this.pickedColor.Blue += amount;
                    else this.pickedColor.Blue = amount;
                    break;
            }
        }

        void CheckSliderClick()
        {
            if (dragging)
            {
                CheckSliderIntersection(currentComponent);

                if (Mouse.Instance.ButtonReleased())
                {
                    dragging = false;
                    Interact();
                }
            }
            
            if (Mouse.Instance.ButtonPressed())
                for (int i = 0; i < 3; i++)
                {
                    if (CheckSliderIntersection((ColorComponent)i))
                    {
                        dragging = true;
                    }
                }
        }

        bool CheckIntersection()
        {
            Vector3 mPos = new Vector3(Mouse.Instance.Position, 0);
            Vector4 tPos = GameMath.VectorAbs(Vector3.Transform(mPos, Matrix.Invert(motion.WorldMatrix)));

            return ((tPos.X <= size.X / 2f) && (tPos.Y <= size.Y / 2f));
        }

        bool CheckSliderIntersection(ColorComponent component)
        {
            Vector3 mPos = new Vector3(Mouse.Instance.Position, 0);
            Vector4 tPos = Vector3.Transform(mPos, Matrix.Invert(motion.WorldMatrix)) - new Vector4(sliderOffset.X,sliderOffset.Y-(int)component*(sliderSize.Y+sliderSpacing), 0, 0);
            Vector4 aPos = GameMath.VectorAbs(tPos);

            bool result = ((aPos.X <= sliderSize.X / 2f) && (aPos.Y <= sliderSize.Y / 2f));
            if (result || ((currentComponent == component) && dragging))
            {
                currentComponent = component;
                CurrentSubElement = (int)component;    
                changeValues(Math.Max(0, Math.Min(1, (sliderSize.X / 2f + tPos.X) / sliderSize.X)));
            }
            
            return result;
        }

        /// <summary>
        /// \internal Invokes the onInteraction event
        /// </summary>
        public override void Interact()
        {
            if (this.OnInteraction != null)
                this.OnInteraction.Invoke(Id, pickedColor);
        }
    }
}
